#include "Texture2DGL3x.h"

using namespace Renderer;
using namespace Renderer::GL3x;


void Renderer::GL3x::Texture2DGL3x::ApplySampler(TextureSampler* sampler)
{
	GLenum minFilter = TypeConverterGL3x::to_gl_type(sampler->GetMinificationFilter());
	GLenum magFilter = TypeConverterGL3x::to_gl_type(sampler->GetMagnificationFilter());
	GLenum wrapS = TypeConverterGL3x::to_gl_type(sampler->GetWrapS());
	GLenum wrapT = TypeConverterGL3x::to_gl_type(sampler->GetWrapT());

	glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, minFilter);
	glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, magFilter);
	glTexParameteri(_target, GL_TEXTURE_WRAP_S, wrapS);
	glTexParameteri(_target, GL_TEXTURE_WRAP_T, wrapT);
}

void Renderer::GL3x::Texture2DGL3x::GenerateMipmaps()
{
	if (_description.IsGernateMipmaps())
	{
		glGenerateMipmap(GL_TEXTURE_2D);
	}
}

void Renderer::GL3x::Texture2DGL3x::VerifyRowAlignment(int rowAlignment)
{
	if ((rowAlignment != 1) &&
		(rowAlignment != 2) &&
		(rowAlignment != 4) &&
		(rowAlignment != 8))
	{
		//throw new ArgumentException("rowAlignment");
		assert(false);
	}
}

void Renderer::GL3x::Texture2DGL3x::Destroy()
{
	if (_name)
	{
		_name->Destroy();
		_name = 0;
	}
}

Renderer::Texture2DDescription Renderer::GL3x::Texture2DGL3x::GetDescription()
{
	return _description;
}

ReadPixelBuffer* Renderer::GL3x::Texture2DGL3x::CopyToBuffer(ImageFormat format, ImageDatatype dataType, int rowAlignment)
{
	if (format == ImageFormat::StencilIndex)
	{
		//throw new ArgumentException("StencilIndex is not supported by CopyToBuffer.  Try DepthStencil instead.", "format");
		assert(false);
	}

	VerifyRowAlignment(rowAlignment);

	ReadPixelBufferGL3x* pixelBuffer = new ReadPixelBufferGL3x(
		PixelBufferHint::Stream,
		TextureUtility::RequiredSizeInBytes(_description.GetWidth(), _description.GetHeight(), format, dataType, rowAlignment));
	pixelBuffer->Bind();
	BindToLastTextureUnit();
	glPixelStorei(GL_PACK_ALIGNMENT, rowAlignment);
	glGetTexImage(_target, 0,
		TypeConverterGL3x::to_gl_type(format),
		TypeConverterGL3x::to_gl_type(dataType),
		0);
	return pixelBuffer;
}

void Renderer::GL3x::Texture2DGL3x::CopyFromBuffer(WritePixelBuffer* pixelBuffer, int xOffset, int yOffset, int width, int height, ImageFormat format, ImageDatatype dataType, int rawAlignment)
{
	if (pixelBuffer->GetSizeInBytes() < TextureUtility::RequiredSizeInBytes(
		width, height, format, dataType, rawAlignment))
	{
		assert(false);
		//throw new ArgumentException("Pixel buffer is not big enough for provided width, height, format, and datatype.");
	}

	if (xOffset < 0)
	{
		assert(false);
		//throw new ArgumentOutOfRangeException("xOffset", "xOffset must be greater than or equal to zero.");
	}

	if (yOffset < 0)
	{
		assert(false);
		//throw new ArgumentOutOfRangeException("yOffset", "yOffset must be greater than or equal to zero.");
	}

	if (xOffset + width > _description.GetWidth())
	{
		assert(false);
		//throw new ArgumentOutOfRangeException("xOffset + width must be less than or equal to Description.Width");
	}

	if (yOffset + height > _description.GetHeight())
	{
		assert(false);
		//throw new ArgumentOutOfRangeException("yOffset + height must be less than or equal to Description.Height");
	}

	VerifyRowAlignment(rawAlignment);
	assert(glGetError() == GL_NO_ERROR);
	WritePixelBufferGL3x* bufferObject = (WritePixelBufferGL3x*)pixelBuffer;
	assert(glGetError() == GL_NO_ERROR);
	bufferObject->Bind();
	BindToLastTextureUnit();
	glPixelStorei(GL_UNPACK_ALIGNMENT, rawAlignment);
	glTexSubImage2D(_target, 0,
		xOffset, yOffset, width, height,
		TypeConverterGL3x::to_gl_type(format),
		TypeConverterGL3x::to_gl_type(dataType),
		0);
	GenerateMipmaps();
	assert(glGetError() == GL_NO_ERROR);
}

void Renderer::GL3x::Texture2DGL3x::UnBind(GLenum textureTarget)
{
	glBindTexture(textureTarget, 0);
}

void Renderer::GL3x::Texture2DGL3x::BindToLastTextureUnit()
{
	glActiveTexture(_lastTextureUnit);
	assert(glGetError() == GL_NO_ERROR);
	Bind();
}

void Renderer::GL3x::Texture2DGL3x::Bind()
{
	glBindTexture(_target, _name->GetValue());
	assert(glGetError() == GL_NO_ERROR);
}

GLenum Renderer::GL3x::Texture2DGL3x::GetTarget() const
{
	return _target;
}

TextureNameGL3x* Renderer::GL3x::Texture2DGL3x::GetHandle() const
{
	return _name;
}

Renderer::GL3x::Texture2DGL3x::~Texture2DGL3x()
{
	//Destroy();
}

Renderer::GL3x::Texture2DGL3x::Texture2DGL3x(const Texture2DDescription& description, GLenum textureTarget)
{
	if (description.GetWidth() <= 0)
	{
		//TODO throw new ArgumentOutOfRangeException("description.Width", "description.Width must be greater than zero.");
		assert(false);
	}

	if (description.GetHeight() <= 0)
	{
		//TODO throw new ArgumentOutOfRangeException("description.Height", "description.Height must be greater than zero.");
		assert(false);
	}

	if (description.IsGernateMipmaps())
	{
		if (textureTarget == GL_TEXTURE_RECTANGLE)
		{
			//throw new ArgumentException("description.GenerateMipmaps cannot be true for texture rectangles.", "description");
			assert(false);
		}

		if (!TextureUtility::IsPowerOfTwo((unsigned int)description.GetWidth()))
		{
			//throw new ArgumentException("When description.GenerateMipmaps is true, the width must be a power of two.", "description");
			assert(false);
		}

		if (!TextureUtility::IsPowerOfTwo((unsigned int)description.GetHeight()))
		{
			//throw new ArgumentException("When description.GenerateMipmaps is true, the height must be a power of two.", "description");
			assert(false);
		}
	}
	assert(glGetError() == GL_NO_ERROR);
	_name = new TextureNameGL3x();
	_target = textureTarget;
	_description = description;
	_lastTextureUnit = GL_TEXTURE0 + (Device::GetInstance()->NumberOfTextureUnits() - 1);

	//assert(glGetError() == GL_NO_ERROR);
	//
	// TexImage2D is just used to allocate the texture so a PBO can't be bound.
	//
	WritePixelBufferGL3x::UnBind();
	assert(glGetError() == GL_NO_ERROR);
	BindToLastTextureUnit();
	assert(glGetError() == GL_NO_ERROR);
	glTexImage2D(_target, 0,
		TypeConverterGL3x::to_gl_type(description.GetTextureFormat()),
		description.GetWidth(),
		description.GetHeight(),
		0,
		TypeConverterGL3x::TextureToPixelFormat(description.GetTextureFormat()),
		TypeConverterGL3x::TextureToPixelType(description.GetTextureFormat()),
		0);
	assert(glGetError() == GL_NO_ERROR);
	//
	// Default sampler, compatiable when attaching a non-mimapped 
	// texture to a frame buffer object.
	//
	ApplySampler(Device::GetInstance()->GetTextureSamplers()->GetLinearClamp());
	assert(glGetError() == GL_NO_ERROR);
}
